// process.c
// Joseph Craig, Rebecca Rush, Steven Andryzcik
// Version 6 - 12/08/2011

#include "mpx_supt.h"
#include "process.h"
#include "inter.h"
#include <dos.h>
#include <stdlib.h>

#define NEXT_PROCESS process_add_count++

/****************************** Global Variables ******************************/
pq
	ready,
	blocked,
	suspended;

unsigned long process_add_count = 0l;

/************************ Utitlity Function Prototypes ************************/
int compare_nodes_fifo(void *, void *);
int compare_nodes_priority(void *, void *);
int delete_data(void *);
int find(void *, int, void **);
int print_pcb(ppcb);
void program_load(int, char *, int);

/***************************** Utility Functions ******************************/
// compare_nodes_priority
// Parameters: two pcbs
// Return: True or False
// Functions called: none
// Compares two pcbs to determine where to place them in the queue
int compare_nodes_priority(void *pcb1, void *pcb2) {
	ppcb a = (ppcb) pcb1;
	ppcb b = (ppcb) pcb2;
	
	return (
		a->priority > b->priority || (
			a->priority == b->priority && a->added < b->added
		) ?
		TRUE :
		FALSE
	);
}

// compare_nodes_fifo
// Parameters: two pcbs
// Return: True or False
// Functions called: none
// Compares two pcbs for fifo order
int compare_nodes_fifo(void *pcb1, void *pcb2) {
	return (((ppcb) pcb1)->added < ((ppcb) pcb2)->added ? TRUE : FALSE);
}

// delete_data
// Parameters: void pointer
// Returns: OK
// Functions called: sys_free_mem
// Frees the memory associated with a block.
int delete_data(void *block) {
	ppcb block_temp = (ppcb) block;

	if(block_temp->load != NULL){
		sys_free_mem(block_temp->load);
	}
	
	sys_free_mem(block);
	
	return OK;
}

// find
// Parameters: block, number of args, process name
// Returns: True or false
// Functions called: none
// Compares process name and if found returns true. 
int find(void *block, int argc, void **argv) {
	argc = argc;
	
	return (strcmp(((ppcb) block)->name, (char *) argv[0]) == 0 ? TRUE : FALSE);
}

// print_pcb
// Parameters: pointer to pcb
// Returns: OK
// Functions called: sprintf, print
// prints an individual pcb
int print_pcb(ppcb to_print) {
	char buffer[BUFSIZ] = "";
	sprintf(
		buffer,
		"Name: %s, Class: %d, Size: %d, Priority: %d, State: %d Added: %d\n",
		to_print->name,
		to_print->class,
		to_print->memory_size,
		to_print->priority,
		to_print->state,
		to_print->added
	);
	print(buffer, FALSE);
	return OK;
}

// program_load
// Parameters: priority, filename, process state
// Return: void
// Functions called: pcb_find, sys_check_program, pcb_setup,
// sys_alloc_mem, pcb_insert, print
// Loads a program into the suspeneded queue.
void program_load(int priority, char *filename, int processState) {
	int errorState;
	int progLenP;
	int startOffsetP;
	
	if(priority < -128 || priority > 127){
		print(
			"\n\tError: The priority must be at least -128 and at most 127.\n\n",
			TRUE
		);
		return;
	}
	if(pcb_find(filename) != NULL){
		print(
			"\n\tError: The process name is not unique.\n\n",
			TRUE
		);
		return;
	}

	if((errorState = sys_check_program("\0", filename, &progLenP, &startOffsetP)) == 0){
		ppcb newPCB;
		context *npc;
		int loadError;
		
		newPCB = pcb_setup(filename, processState, priority);
		newPCB->load = (unsigned char *)sys_alloc_mem(progLenP); 
		newPCB->execute = newPCB->load + startOffsetP;
		newPCB->memory_size = progLenP;
		
		npc = (context*) newPCB->stack_top;
		npc->IP = FP_OFF(newPCB->execute); 
		npc->CS = FP_SEG(newPCB->execute);
		npc->FLAGS = 0x200;
		npc->DS = _DS;
		npc->ES = _ES;
		
		if((loadError = sys_load_program((void *)newPCB->load, newPCB->memory_size, "\0", filename)) == 0){
			if(processState == SYSTEM){
				pcb_insert(READY, newPCB);
			}
			else{
				pcb_insert(SUSPENDED, newPCB);
			}
		}
		else if(loadError == ERR_SUP_NAMLNG){
			print(
				"\n\tError: The pathname is too long.\n\n",
				TRUE
			);
			return;
		}
		else if(loadError == ERR_SUP_FILNFD){
			print(
				"\n\tError: File not found.\n\n",
				TRUE
			);
			return;
		}
		else if(loadError == ERR_SUP_FILINV){
			print(
				"\n\tError: File is not valid.\n\n",
				TRUE
			);
			return;
		}
		
		else if (loadError == ERR_SUP_PROGSZ){
			print(
				"\n\tError: Program too big for region.\n\n",
				TRUE
			);
			return;
		}
		else if (loadError == ERR_SUP_LDADDR){
			print(
					"\n\tError: Invalid load address.\n\n",
					TRUE
				);
				return;
		}
		else if (loadError == ERR_SUP_LDFAIL){
			print(
				"\n\tError: Load Failed.\n\n",
				TRUE
			);
			return;
		}
	}
	else if(errorState == ERR_SUP_NAMLNG){
		print(
			"\n\tError: The pathname is too long.\n\n",
			TRUE
		);
		return;
	}
	else if(errorState == ERR_SUP_FILNFD){
		print(
			"\n\tError: File not found.\n\n",
			TRUE
		);
		return;
	}
	else if(errorState == ERR_SUP_FILINV){
		print(
			"\n\tError: File is not valid.\n\n",
			TRUE
		);
		return;
	}
}

/***************************** Process Functions ******************************/

// empty_proces_queues
// Parameters: pointer to determine what it fails on
// Return: error code
// Functions called: pq_free
// Empties the process queues
int empty_process_queues(int *failed_on) {
	int error_code;
	
	if ((error_code = pq_free(&ready)) != OK) {
		*failed_on = READY;
	} else if ((error_code = pq_free(&blocked)) != OK) {
		*failed_on = BLOCKED;
	} else if ((error_code = pq_free(&suspended)) != OK) {
		*failed_on = SUSPENDED;
	}
	return error_code;
}

// initialize_proces_queues
// Parameters: pointer to determine what it fails on
// Return: error code
// Functions called: pq_init
// Initializes the process queues
int initialize_process_queues(int *failed_on) {
	int error_code;
	
	if (
		(error_code = pq_init(
			&ready,
			compare_nodes_priority,
			delete_data,
			find
		)) != OK
	) {
		*failed_on = READY;
	} else if (
		(error_code = pq_init(
			&blocked,
			compare_nodes_fifo,
			delete_data,
			find
		)) != OK
	) {
		*failed_on = BLOCKED;
	} else if (
		(error_code = pq_init(
			&suspended,
			compare_nodes_priority,
			delete_data,
			find
		)) != OK
	) {
		*failed_on = SUSPENDED;
	} 
	return error_code;
}

// pcb_allocate
// Paramaters: none
// Returns: pointer to the new PCB
// This aglorithm uses the MPX support function sys_alloc_mem.
// Allocates the memory for a new PCB.
ppcb pcb_allocate()
{
	int size = sizeof(pcb);
	ppcb pcb_ptr;
	pcb_ptr = sys_alloc_mem(size);
	return(pcb_ptr);
}

// make_empty
// Paramaters: none
// Returns: void
// Functions called: pq_make_empty
// Empties the queues
void make_empty(){
	pq_make_empty(&ready);
	pq_make_empty(&blocked);
	pq_make_empty(&suspended);
}

// pcb_find
// Paramaters: Name of PCB.
// Returns: pointer to the PCB
// This aglorithm checks all four queues: Ready, Blocked, Suspended Ready, and Suspended Blocked.
// Note, return value of null could mean name invalid or not found
// Searches all queues for PCB with a given name.
ppcb pcb_find(char *pname) {

	ppcb temp = NULL;
	
	if(pq_find(&ready, 1, (void **)&pname, (void **)&temp) == OK)
	{
		return temp;	
	}
	else if (pq_find(&blocked, 1, (void **)&pname, (void **)&temp) == OK)
	{
		return temp;
	}
	else if (pq_find(&suspended, 1, (void **)&pname, (void **)&temp) == OK)
	{
		return temp;
	}
	else
	{
		return NULL;
	}
}

// pcb_free
// Paramaters: pointer to the PCB.
// Returns: nothing.
// This aglorithm uses the MPX support function sys_free_mem.
// Frees the memory asociated with a PCB.
void pcb_free(ppcb ptr)
{
	delete_data((void *)ptr);
} 

// pcb_insert
// Paramaters: new state and the pointer to PCB.
// Returns: nothing.
// Functions called: pq_enqueue
// This aglorithm uses a switch case to determine which queue to insert the PCB into.
// Inserts PCB into the correct queue.
void pcb_insert(int newState, ppcb pcbToInsert){
	pcbToInsert->state = newState;
	
	if (pcbToInsert->class != SYSTEM) {
		pcbToInsert->added = NEXT_PROCESS;
	}

	if(newState == READY){
		pq_enqueue(&ready, (void *)pcbToInsert);
	}
	else if(newState == BLOCKED){
		pq_enqueue(&blocked, (void *)pcbToInsert);
	}
	else if(newState == SUSPENDED){
		pq_enqueue(&suspended, (void *)pcbToInsert);
	}
	else{
		print("The destination of the process is invalid\n", TRUE);
	}
}

// pcb_remove
// Paramaters: pointer to pcb.
// Returns: nothing.
// Functions called: pq_remove
// This aglorithm makes use of the Find_PCB method. 
// Removes the PCB from it's current queue. 
void pcb_remove(ppcb pcbToRemove){

	int state = pcbToRemove->state;

	if(state == READY){
		pq_remove(&ready, (void *)pcbToRemove);
	}
	else if(state == BLOCKED){
		pq_remove(&blocked, (void *)pcbToRemove);
	}
	else if(state == SUSPENDED){
		pq_remove(&suspended, (void *)pcbToRemove);
	}
	else{
		print("The state assoicated with this process is incorrect\n", TRUE);
	}
}

// pcb_resume
// Paramaters: process name
// Returns: nothing.
// Functions called: pcb_remove, pcb_insert
// Resumes the pcb
int pcb_resume(char *name){
	ppcb myPCB;
	myPCB = pcb_find(name);
	
	if(myPCB == NULL)
	{
		return ERR_PQUEUE_ITEMNOTFOUND;
	}
	else if(myPCB->class == SYSTEM){
		return ERR_CHANGE_OF_SYSTEM_PROCESS;
	}
	pcb_remove(myPCB);
	pcb_insert(READY, myPCB);
	return OK;
}

// pcb_set_priority
// Paramaters: name, priority
// Returns: OK or error
// Functions called: pcb_remove, pcb_insert 
// Sets a pcb's priority
int pcb_set_priority(char *name, int priority){

	ppcb temp = pcb_find(name);
	
	if (temp == NULL) {
		return ERR_PQUEUE_ITEMNOTFOUND;
	}
	else if(temp->class == SYSTEM){
		return ERR_CHANGE_OF_SYSTEM_PROCESS;
	}
	temp->priority = priority;	
	pcb_remove(temp);
	pcb_insert(temp->state, temp);
	return OK;
}

// pcb_setup
// Paramaters: name, priority, class
// Returns: pointer to the PCB
// This aglorithm uses the Allocate_PCB function. 
// Initializes a PCB's content.
ppcb pcb_setup(char *name, int class, int priority) {
	int i;
	ppcb newPCB;
	
	if(pcb_find(name) != NULL){
		print("Name of process is already taken\n", TRUE);
		return NULL;
	}
	if(priority < -128 || priority > 127){
		print("Priority should be in the range -128 to 127\n", TRUE);
		return NULL;
	}
	if(class != SYSTEM && class != APPLICATION){
		print("Class must be set to 1 for system or 2 for application\n", TRUE);
		return NULL;
	}
	
	newPCB = pcb_allocate();
	strcpy(newPCB->name, name);
	newPCB->priority = priority;
	newPCB->class = class;
	newPCB->state = READY;
	newPCB->memory_size = 0; 
	newPCB->added = 0;
	newPCB->load = NULL;
	newPCB->execute = NULL;
	
	newPCB->stack_top = newPCB->stack_base + PCB_STACK_SIZE - sizeof(context);
	
	for (i = 0; i < PCB_STACK_SIZE; i++) {
		newPCB->stack_base[i] = 0;
	}
	
	return newPCB;
}

// pcb_show
// Parameters: name
// Return: OK
// Functions called: print_pcb
// prints a pcb
int pcb_show(char *name){
	ppcb myPCB = pcb_find(name);
	if (myPCB == NULL) {
		return ERR_PQUEUE_ITEMNOTFOUND;
	}
	print_pcb(myPCB);
	return OK;
}

// pcb_show_blocked
// Parameters: none
// Return: OK or error code
// Functions called: print_pcb, print
// prints pcbs in blocked queue
int pcb_show_blocked(){
	int   errorState;
	ppcb  temp;
	errorState = pq_start_iteration(&blocked);
	if(errorState == OK){
		print("\nProcesses in the Blocked Queue:\n", FALSE);
		while(pq_get_next(&blocked, (void **)&temp) == OK) {			
			// Write the PCB's information to the terminal.
			print_pcb(temp);
		}
		return OK;
	} 
	else if(errorState == ERR_PQUEUE_EMPTYQUEUE){
		return ERR_PQUEUE_EMPTYQUEUE;
	}
	else{
		return ERR_GEN_OUTOFMEMORY;
	}
}

// pcb_show_ready
// Parameters: none
// Return: OK or error code
// Functions called: print_pcb, print
// prints pcbs in ready queue
int pcb_show_ready() {
	int   errorState;
	ppcb  temp;
	
	errorState = pq_start_iteration(&ready);
	if(errorState == OK){
		print("\nProcesses in the Ready Queue:\n", FALSE);
		while(pq_get_next(&ready, (void **)&temp) == OK) {
			// Write the PCB's information to the terminal.
			print_pcb(temp);
		}
		return OK;
	} 
	else if(errorState == ERR_PQUEUE_EMPTYQUEUE){
		return ERR_PQUEUE_EMPTYQUEUE;
	}
	else{
		return ERR_GEN_OUTOFMEMORY;
	}
}

// pcb_show_suspended
// Parameters: none
// Return: OK or error code
// Functions called: print_pcb, print
// prints pcbs in suspended queue
int pcb_show_suspended(){
	int   errorState;
	ppcb  temp;
	
	errorState = pq_start_iteration(&suspended);
	if(errorState == OK){
		print("\nProcesses in the Suspended Queue:\n", FALSE);
		while(pq_get_next(&suspended, (void **)&temp) == OK) {
			// Write the PCB's information to the terminal.
			print_pcb(temp);
		}
		return OK;
	} 
	else if(errorState == ERR_PQUEUE_EMPTYQUEUE){
		return ERR_PQUEUE_EMPTYQUEUE;
	}
	else{
		return ERR_GEN_OUTOFMEMORY;
	}
}

